package com.tianji.learning.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.course.CourseSimpleInfoDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningPlanDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.domain.vo.LearningLessonVO;
import com.tianji.learning.domain.vo.LearningPlanPageVO;
import com.tianji.learning.domain.vo.LearningPlanVO;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.PlanStatus;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.service.ILearningRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * <p>
 * 学生课程表 服务实现类
 * </p>
 *
 * @author root
 * @since 2023-10-19
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    final CourseClient courseClient;
    final CatalogueClient catalogueClient;
    // 不能注入，会出现循环注入的问题，可以注入对应的Mapper
    //final ILearningRecordService recordService;
    final LearningRecordMapper recordMapper;

    @Override
    public void addUserLesson(Long userId, List<Long> courseIds) {
        // 1. 通过 feign 远程调用课程服务，得到课程信息
        List<CourseSimpleInfoDTO> cinfos = courseClient.getSimpleInfoList(courseIds);
        // 2. 封装 po 实体类，填充过期时间
        List<LearningLesson> list = new ArrayList<>();
        for (CourseSimpleInfoDTO cinfo : cinfos){
            LearningLesson lesson = new LearningLesson();
            lesson.setUserId(userId);
            lesson.setCourseId(cinfo.getId());
            Integer validDuration = cinfo.getValidDuration(); //课程有效期，单位月
            if(validDuration != null){
                LocalDateTime now = LocalDateTime.now();
                lesson.setCreateTime(now);
                lesson.setExpireTime(now.plusMonths(validDuration));
            }
            list.add(lesson);
        }
        // 3. 批量保存
        this.saveBatch(list);
    }

    @Override
    public PageDTO<LearningLessonVO> queryMyLessons(PageQuery query) {

        // 1. 获取当前登录的用户
        Long userId = UserContext.getUser();
        // 2. 分页查询我的课表
        Page<LearningLesson> page = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId) //需要查询当前登录人的课程
                .page(query.toMpPage("latest_learn_time", false)); // 配置默认排序字段和方法
        List<LearningLesson> records = page.getRecords();
           // 判断是否有课程
        if(CollUtil.isEmpty(records)){
            return PageDTO.empty(page);
        }
        // 3. 远程调用课程服务，给 vo 中的 课程名，封面，章节数赋值
        Set<Long> courseIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());
        List<CourseSimpleInfoDTO> cinfos = courseClient.getSimpleInfoList(courseIds);
        if(CollUtil.isEmpty(cinfos)){
            throw new BizIllegalException("课程不存在");
        }
        // 将 cinfos 课程集合转换为 map 结构<课程id, 课程对象>
        Map<Long, CourseSimpleInfoDTO> infoDTOMap = cinfos.stream()
                .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));

        // 4. 将 po 中的数据封装到 vo 中
        ArrayList<LearningLessonVO> voList = new ArrayList<>();
        for(LearningLesson record : records){
            LearningLessonVO vo = BeanUtils.copyBean(record,LearningLessonVO.class);
            CourseSimpleInfoDTO infoDTO = infoDTOMap.get(record.getCourseId());
            if(infoDTO != null){
                vo.setCourseName(infoDTO.getName());
                vo.setCourseCoverUrl(infoDTO.getCoverUrl());
                vo.setSections(infoDTO.getSectionNum());
            }
            voList.add(vo);
        }
        // 5. 返回
        return PageDTO.of(page,voList);
    }

    @Override
    public LearningLessonVO queryMyCurrentLesson() {

        // 1. 获取当前登录用户 id
        Long userId = UserContext.getUser();
        // 2. 查询当前用户最近学习课程  按latest_learn_time 降序排序  正在学习中（status=1）
        // sql: select * from learning_lesson where user_id=xxx and status=1 order by latest_learn_time desc limit 1;
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, LessonStatus.LEARNING)
                .orderByDesc(LearningLesson::getLatestLearnTime)
                .last("limit 1")
                .one();
        if(lesson == null){
            return null;
        }
        // 3. 远程调用课程服务，给 vo 中的 课程名，封面，章节数赋值
        CourseFullInfoDTO cinfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
        if(cinfo == null){
            throw new BizIllegalException("课程不存在");
        }
        // 4. 查询当前用户课表中总课程数
        // sql: select count(*) from learn_lesson where user_id = xxx;
        Integer count = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .count();
        // 5. 通过 feign 远程调用课程服务，获取小节名称与编号
        Long latestSectionId = lesson.getLatestSectionId();//最近学习的小节id
        List<CataSimpleInfoDTO> cataSimpleInfoDTOS = catalogueClient.batchQueryCatalogue(CollUtils.singletonList(latestSectionId));
        if(CollUtil.isEmpty(cataSimpleInfoDTOS)){
            throw new BizIllegalException("小节不存在");
        }
        // 6. 封装到 vo,返回
        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        vo.setCourseName(cinfo.getName());
        vo.setCourseCoverUrl(cinfo.getCoverUrl());
        vo.setSections(cinfo.getSectionNum());
        vo.setCourseAmount(count);//当前用户能学习的课程总数
        CataSimpleInfoDTO cataSimpleInfoDTO = cataSimpleInfoDTOS.get(0);
        vo.setLatestSectionName(cataSimpleInfoDTO.getName());//最近学习的小节名称
        vo.setLatestSectionIndex(cataSimpleInfoDTO.getCIndex());//最近学习的小节序号

        return vo;
    }

    @Override
    public LearningLessonVO queryLessonByCourseId(Long courseId) {
        // 1. 获取当前登录用户id
        Long userId = UserContext.getUser();
        // 2. 检查当前用户的课表中是否有该课程,判断课程状态是否有效(与当前时间进行比较)
        LocalDateTime now = LocalDateTime.now();
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId,courseId)
                .one() ;
        if(lesson == null){
            return null;
        }
        // 3. po 封装 vo 返回
        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        return vo;
    }

    @Override
    public Long isLessonValid(Long courseId) {
        // 1. 获取当前登录用户id
        Long userId = UserContext.getUser();
        // 2. 检查当前用户的课表中是否有该课程,判断课程状态是否有效(与当前时间进行比较)
        LocalDateTime now = LocalDateTime.now();
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId,courseId)
                .one() ;
        if(lesson == null){
            return null;
        }
        // 3. 有效返回 lessonId 否则返回null
        LocalDateTime expireTime = lesson.getExpireTime();
        if(expireTime != null && now.isAfter(expireTime)){
            return null;
        }

        return lesson.getId();
    }



    @Override
    public void createLearningPlan(LearningPlanDTO dto) {
        // 1. 获取当前登录用户 id
        Long userId = UserContext.getUser();
        // 2. 获取当前课表
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getCourseId, dto.getCourseId())
                .eq(LearningLesson::getUserId, userId)
                .one();
        if(lesson == null){
            throw new BizIllegalException("该课程没加入课表");
        }
        // 3. 修改课表
//        lesson.setWeekFreq(dto.getFreq());
//        lesson.setPlanStatus(PlanStatus.PLAN_RUNNING);
//        this.updateById(lesson);
        // 链式编程
        this.lambdaUpdate()
                .set(LearningLesson::getWeekFreq,dto.getFreq())
                .set(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .eq(LearningLesson::getId,lesson.getId())
                .update();

    }

    @Override
    public LearningPlanPageVO queryMyPlans(PageQuery query) {
        // 1. 获取当前登录用户 id
        Long userId = UserContext.getUser();
        // todo 2. 查询积分
        // 3. 查询本周学习计划总数据  查询表: learning_lesson  条件 userId | status in(0.1) | plan_status=1 | sum(week_freq)//某个用户每周需要学习小节总数
        // sql: select sum(week_freq) from learning_lesson where user_id=2 and plan_status=1 and status in(0,1);
        QueryWrapper<LearningLesson> wrapper = new QueryWrapper<>();
        wrapper.select("sum(week_freq) as plansTotal");//查询那些列
        wrapper.eq("user_id",userId);
        wrapper.in("status",LessonStatus.NOT_BEGIN,LessonStatus.LEARNING);
        wrapper.eq("plan_status",PlanStatus.PLAN_RUNNING);
        Map<String, Object> map = this.getMap(wrapper);
        //{plansTotal: 7}
        Integer plansTotal = 0;
        if(map != null && map.get("plansTotal") != null){
            plansTotal = Integer.valueOf(map.get("plansTotal").toString());
        }

        // 4. 查询本周已学习计划总数据  查询表: learning_record  条件: userId | finish_time(本周内) 返回: count()
        // sql: select * from learning_record where user_id=2 and finish_time between '2023-05-22 00:00:01' and '2023-05-28 23:59:59';
        LocalDate now = LocalDate.now();
        LocalDateTime weekBeginTime = DateUtils.getWeekBeginTime(now);
        LocalDateTime weekEndTime = DateUtils.getWeekEndTime(now);
        Integer weekFinishedPlanNum = recordMapper.selectCount(Wrappers.<LearningRecord>lambdaQuery()
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getFinished, true)
                .between(LearningRecord::getFinishTime, weekBeginTime, weekEndTime));
        // 5. 查询课表数据 learing_lesson 条件 userId | status in(0.1) | plan_status=1 分页
        Page<LearningLesson> page = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .page(query.toMpPage("latest_learn_time", false));
        List<LearningLesson> records = page.getRecords();
        if(CollUtils.isEmpty(records)){
            // 返回一个空的 vo 对象，防止前端直接调用产生错误
            LearningPlanPageVO vo = new LearningPlanPageVO();
            vo.setTotal(0L);
            vo.setPages(0L);
            vo.setList(CollUtils.emptyList());
            return vo;
        }

        // 6. 远程调用课程服务 获取课程信息
        // 得到所有计划课程的 id
        Set<Long> courseIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());
        // 通过计划课程的 id 得到 对应课程的信息
        List<CourseSimpleInfoDTO> cinfos = courseClient.getSimpleInfoList(courseIds);
        if(CollUtils.isEmpty(cinfos)){
            throw new BizIllegalException("课程不存在");
        }
        // 将 cinfos list 转换为 map <课程id, CourseSimpleInfoDTO>
        Map<Long, CourseSimpleInfoDTO> cinfosMap = cinfos.stream().collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));

        // 7. 查询当前用户本周每一门课已学习的小节数量 learning_record  条件 userId | finished = 1 | finish_time(本周内)
        // sql: select lesson_id, count(*) from learning_record where user_id=2 and finished = 1 and finish_time between '2023-05-22 00:00:01' and '2023-05-28 23:59:59' group by lesson_id;
        QueryWrapper<LearningRecord> rWrapper = new QueryWrapper<>();
        rWrapper.select("lesson_id as lessonId","count(*) as userId");//由于没有count属性，因此可以使用一个临时的属性存储该值
        rWrapper.eq("user_id",userId);
        rWrapper.eq("finished",true);
        rWrapper.between("finish_time",weekBeginTime,weekEndTime);
        rWrapper.groupBy("lesson_id");
        List<LearningRecord> learningRecords = recordMapper.selectList(rWrapper);
        // 把 learningRecords 转 map <课表id, 当前用户本周对该课程已学习的小节数量>
        Map<Long, Long> courseWeekFinishNumMap = learningRecords.stream().collect(Collectors.toMap(LearningRecord::getLessonId, c -> c.getUserId()));

        // 8. 封装 vo 返回
        LearningPlanPageVO vo = new LearningPlanPageVO();
        vo.setWeekTotalPlan(plansTotal);
        vo.setWeekFinished(weekFinishedPlanNum);
        List<LearningPlanVO> voList = new ArrayList<>();
        for (LearningLesson record : records){
            LearningPlanVO planVO = BeanUtils.copyBean(record, LearningPlanVO.class);
            CourseSimpleInfoDTO infoDTO = cinfosMap.get(record.getCourseId());
            if(infoDTO != null){
                planVO.setCourseName(infoDTO.getName());//课程名
                planVO.setSections(infoDTO.getSectionNum());//课程下总小节数
            }

            Long aLong = courseWeekFinishNumMap.get(record.getId());
            if(aLong != null){
                planVO.setWeekLearnedSections(aLong.intValue());
            }else {
                planVO.setWeekLearnedSections(0);//本周已学习的章节数
            }

            voList.add(planVO);
        }

        return vo.pageInfo(page.getTotal(),page.getPages(),voList);
    }
}
